﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Drawing.Drawing2D;
using My25348855.DNA;

namespace IMLibrary.Controls.RichTextBox
{ 
    /// <summary>
    /// 
    /// </summary>
    public class MyExtRichTextBox :System.Windows.Forms. RichTextBox
    {
        private RichEditOle richEditOle; 
        /// <summary>
        /// 
        /// </summary>
        public MyExtRichTextBox()
            : base()
        { 
            this.VScroll += new EventHandler(TRichTextBox_VScroll);
            this.SizeChanged += new EventHandler(TRichTextBox_SizeChanged);
        }
         
        #region excode
        // The following settings can be changed to read from configuration file.
        const int MaximumTextLength = 10000;
        const int TextLengthToBeRemoved = 3000;
        const int MaximumNoOfControl = 50;
        const int NoOfControlToBeRemoved = 20;
        bool KeepShort = true;

        [DllImport("user32.dll")]
        private static extern int SendMessage(IntPtr hwnd, Int32 wMsg, Int32 wParam, ref Point pt);
        const int EM_GETSCROLLPOS = 0x0400 + 221;

        List<MetaInfo> ControlList = new List<MetaInfo>();

        //Contains the initial position data of a control relative to the text content.
        internal class MetaInfo
        {
            int charIndex;
            public int CharIndex
            {
                get { return charIndex; }
                set { charIndex = value; }
            }

            int deltaY;
            public int DeltaY
            {
                get { return deltaY; }
                set { deltaY = value; }
            }

            Control theControl;
            public Control TheControl
            {
                get { return theControl; }
                set { theControl = value; }
            }

            public MetaInfo(Control theControl)
            {
                this.theControl = theControl;
            }

        }

        public void AddControl(Control oneControl)
        {
            // Obtain the initial metadata.
            MetaInfo one = new MetaInfo(oneControl);

            base.Controls.Add(oneControl);
            one.CharIndex = this.TextLength;
            one.TheControl.Location = this.GetPositionFromCharIndex(one.CharIndex);
            one.DeltaY = this.GetPositionFromCharIndex(0).Y - one.TheControl.Location.Y;
            ControlList.Add(one);


            //"Push" the text away from the space occupied by the control.
            do
            {
                this.AppendText(Environment.NewLine);
            }
            while (this.GetPositionFromCharIndex(this.TextLength).Y < (oneControl.Location.Y + oneControl.Height));

            RemoveSome();
            AutoScroll();
        }

        public void AutoScroll()
        {
            this.SelectionStart = this.TextLength - 1;
            this.ScrollToCaret();
        }

        private void RemoveSome()
        {
            //Optional. 
            //Remove some text and control if too many, to release system resources and improve performance.
            if (!KeepShort)
            {
                return;
            }

            int texttoRemove = 0;
            int imgtoRemove = 0;
            try
            {
                if (this.TextLength > MaximumTextLength)
                {
                    texttoRemove = TextLengthToBeRemoved;
                    this.Text = this.Text.Substring(texttoRemove);
                    texttoRemove += this.Text.IndexOf("\n");
                    if (texttoRemove > TextLengthToBeRemoved)
                    {
                        this.Text = this.Text.Substring(texttoRemove - TextLengthToBeRemoved);
                    }

                    foreach (MetaInfo oldone in ControlList)
                    {
                        if (oldone.CharIndex < texttoRemove)
                        {
                            imgtoRemove++;
                        }
                        else
                        {
                            oldone.CharIndex -= texttoRemove;
                        }
                    }

                    for (int i = 0; i < imgtoRemove; i++)
                    {
                        this.Controls[0].Dispose();
                        ControlList.RemoveAt(0);
                    }
                    //need to calculate the metadata again.
                    CalculateDelta();
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }

            try
            {
                if (ControlList.Count > MaximumNoOfControl)
                {
                    imgtoRemove = NoOfControlToBeRemoved;
                    for (int i = 0; i < imgtoRemove; i++)
                    {
                        texttoRemove = ControlList[0].CharIndex;
                        ControlList.RemoveAt(0);
                        this.Controls[0].Dispose();
                    }
                    this.Text = this.Text.Substring(texttoRemove);
                    foreach (MetaInfo oldone in ControlList)
                    {
                        oldone.CharIndex -= texttoRemove;
                    }
                    //need to calculate the metadata again.
                    CalculateDelta();
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        private void TRichTextBox_VScroll(object sender, EventArgs e)
        {
            Point pt = new Point();
            SendMessage(this.Handle, EM_GETSCROLLPOS, 0, ref pt);

            foreach (MetaInfo one in ControlList)
            {
                one.TheControl.Location = new Point(one.TheControl.Location.X, -pt.Y - one.DeltaY);
            }
        }

        private void TRichTextBox_SizeChanged(object sender, EventArgs e)
        {
            CalculateDelta();
        }

        private void CalculateDelta()
        {
            foreach (MetaInfo one in ControlList)
            {
                one.TheControl.Location = this.GetPositionFromCharIndex(one.CharIndex);
                one.DeltaY = this.GetPositionFromCharIndex(0).Y - one.TheControl.Location.Y;
            }
        }

        private void TRichTextBox_LinkClicked(object sender, LinkClickedEventArgs e)
        {
            System.Diagnostics.Process.Start(e.LinkText);
        }
        #endregion

        /// <summary>
        /// 
        /// </summary>
        /// <param name="control"></param>
        public void InsertControl(Control control)
        {
            RichEditOle.InsertControl(control);
        }

        #region 属性
        private RichEditOle RichEditOle
        {
            get
            {
                if ( richEditOle == null)
                {
                    if (base.IsHandleCreated)
                    {
                         richEditOle = new RichEditOle(this);
                    }
                }

                return  richEditOle;
            }
        }
        #endregion 

        #region RichTextBoxPlus Members
        protected IRichEditOle IRichEditOleValue = null;
        protected IntPtr IRichEditOlePtr = IntPtr.Zero;
        public IRichEditOle GetRichEditOleInterface()
        {
            if (this.IRichEditOleValue == null)
            {
                // Allocate the ptr that EM_GETOLEINTERFACE will fill in.
                IntPtr ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(IntPtr)));	// Alloc the ptr.
                Marshal.WriteIntPtr(ptr, IntPtr.Zero);	// Clear it.
                try
                {
                    if (0 != API.SendMessage(this.Handle, RichEditOle.EM_GETOLEINTERFACE, IntPtr.Zero, ptr))
                    {
                        // Read the returned pointer.
                        IntPtr pRichEdit = Marshal.ReadIntPtr(ptr);
                        try
                        {
                            if (pRichEdit != IntPtr.Zero)
                            {
                                // Query for the IRichEditOle interface.
                                Guid guid = new Guid("00020D00-0000-0000-c000-000000000046");
                                Marshal.QueryInterface(pRichEdit, ref guid, out this.IRichEditOlePtr);

                                // Wrap it in the C# interface for IRichEditOle.
                                this.IRichEditOleValue = (IRichEditOle)Marshal.GetTypedObjectForIUnknown(this.IRichEditOlePtr, typeof(IRichEditOle));
                                if (this.IRichEditOleValue == null)
                                {
                                    throw new Exception("Failed to get the object wrapper for the interface.");
                                }
                            }
                            else
                            {
                                throw new Exception("Failed to get the pointer.");
                            }
                        }
                        finally
                        {
                            Marshal.Release(pRichEdit);
                        }
                    }
                    else
                    {
                        throw new Exception("EM_GETOLEINTERFACE failed.");
                    }
                }
                catch
                {
                    this.ReleaseRichEditOleInterface();
                }
                finally
                {
                    // Free the ptr memory.
                    Marshal.FreeCoTaskMem(ptr);
                    //Marshal.DestroyStructure(ptr, typeof(REOBJECT));
                }
            }
            return this.IRichEditOleValue;
        }

        /// <summary>
        /// Releases the IRichEditOle interface if it hasn't been already.
        /// </summary>
        /// <remarks>This is automatically called in Dispose if needed.</remarks>
        public void ReleaseRichEditOleInterface()
        {
            if (this.IRichEditOlePtr != IntPtr.Zero)
            {
                Marshal.Release(this.IRichEditOlePtr);
            }

            this.IRichEditOlePtr = IntPtr.Zero;
            this.IRichEditOleValue = null;
        }

        #endregion
      
        #region 扩展

        #region 清空图片
        /// <summary>
        /// 清空图片
        /// </summary>
        public void ClearImage()
        {
            REOBJECT reObject = new REOBJECT();
            for (int i = 0; i < this.GetRichEditOleInterface().GetObjectCount(); i++)
            {
                this.GetRichEditOleInterface().GetObject(i, reObject, GETOBJECTOPTIONS.REO_GETOBJ_ALL_INTERFACES);
                object t = Marshal.GetObjectForIUnknown(reObject.poleobj);
                var pic = t as ExPictureBox;
                if (pic != null)
                    pic.Dispose();
            }
        }
        #endregion

        #region 添加图片到richtextbox
        /// <summary>
        /// 添加图片到richtextbox
        /// </summary>
        /// <param name="MD5"></param>
        /// <param name="image"></param>
        /// <returns></returns>
        public void  AddImage(string MD5, Image image)
        {
            var exsit = GetImage(MD5);
            if (exsit == null)
            {
                exsit = image;
                lock (Images)
                    Images.Add(MD5, image);
            }

            var pic = new ExPictureBox();
            pic.MD5 = MD5;
            pic.Image = exsit;
            Pics.Add(pic);
            RichEditOle.InsertControl(pic);
            Invalidate();
        } 
        #endregion

        #region 获得richtextBox中现有的图片集合
        /// <summary>
        /// 获得richtextBox中现有的图片集合
        /// </summary>
        /// <returns></returns>
        private List<ExPictureBox> GetExistImages()
        {
            List<ExPictureBox> tempGifs = new List<ExPictureBox>();
            REOBJECT reObject = new REOBJECT();
            var count=this.GetRichEditOleInterface().GetObjectCount();
            for (int i = 0; i < count; i++) 
            {
                this.GetRichEditOleInterface().GetObject(i, reObject, GETOBJECTOPTIONS.REO_GETOBJ_ALL_INTERFACES);
                object t = Marshal.GetObjectForIUnknown(reObject.poleobj);
                if(t is ExPictureBox) 
                {
                    var pic = (ExPictureBox)t;
                    pic.Pos = reObject.cp;  
                    tempGifs.Add(pic); 
                } 
            }
            return tempGifs;
        }
        /// <summary>
        /// 获得所有存在图片的MD5值集合
        /// </summary>
        /// <returns></returns>
        public List<string> GetExistImageMD5s()
        {
            List<string> md5s = new List<string>();
            REOBJECT reObject = new REOBJECT();
            for (int i = 0; i < this.GetRichEditOleInterface().GetObjectCount(); i++)
            {
                this.GetRichEditOleInterface().GetObject(i, reObject, GETOBJECTOPTIONS.REO_GETOBJ_ALL_INTERFACES);
                object t = Marshal.GetObjectForIUnknown(reObject.poleobj);
                var pic = t as ExPictureBox;
                if (pic != null)
                {
                    pic.Pos = reObject.cp;
                    md5s.Add(pic.MD5);
                }
            }
            return md5s;
        }
        #endregion

        #region 获得richtextBox中现有的图片集合文本信息
        /// <summary>
        /// 获得richtextBox中现有的图片集合
        /// </summary>
        /// <returns></returns>
        public string GetImageInfo()
        {
            string imageInfo = "";
            var images = GetExistImages();
            if (images != null)
                foreach (var image in images)
                    imageInfo += image.Pos.ToString() + "," + image.MD5+";";
            return imageInfo;
        }
        #endregion

        #region 更新未更新的图片
        /// <summary>
        /// 更新未更新的图片
        /// </summary>
        /// <param name="MD5"></param>
        /// <param name="image"></param>
        public void UpdateImage(string MD5, Image image)
        {
            foreach (var pic in Pics.ToArray())
                if (pic.MD5 == MD5 && !pic.IsLoad)
                {
                    pic.Image = image;
                    pic.IsLoad = true;
                    pic.Invalidate();

                    if (Images.ContainsKey(MD5))
                        Images[MD5] = image;
                }
        }
        #endregion

        #region 图片集合
        /// <summary>
        /// 图片控件集合
        /// </summary>
        private List<ExPictureBox> Pics = new  List<ExPictureBox>();
        /// <summary>
        /// 图片集合
        /// </summary>
        private Dictionary<string, Image> Images = new Dictionary<string,Image>();
        /// <summary>
        /// 获取图片
        /// </summary>
        /// <param name="MD5"></param>
        /// <returns></returns>
        public Image GetImage(string MD5)
        {
            if (Images == null) return null;
            Image image = null;
            Images.TryGetValue(MD5, out image); 
            return image;
        }
        #endregion

        #endregion 
    }

    #region RichEditOle
    internal class RichEditOle
    {
        private MyExtRichTextBox _richEdit;
        private IRichEditOle _richEditOle;
        public const int WM_USER = 0x0400;
        public const int EM_GETOLEINTERFACE = WM_USER + 60;

        public RichEditOle(MyExtRichTextBox richEdit)
        {
            _richEdit = richEdit;
        }

        public IRichEditOle IRichEditOle
        {
            get
            {
                if (_richEditOle == null)
                {
                    _richEditOle = NativeMethods.SendMessage(
                        _richEdit.Handle, NativeMethods.EM_GETOLEINTERFACE, 0);
                }
                return _richEditOle;
            }
        }

        public void InsertControl(Control control)
        {
            if (control == null) return;
            ILockBytes bytes;
            IStorage storage;
            IOleClientSite site;
            Guid guid = Marshal.GenerateGuidForType(control.GetType());
            NativeMethods.CreateILockBytesOnHGlobal(IntPtr.Zero, true, out bytes);
            NativeMethods.StgCreateDocfileOnILockBytes(bytes, 0x1012, 0, out storage);
            IRichEditOle.GetClientSite(out site);
            REOBJECT lpreobject = new REOBJECT();
            lpreobject.cp = _richEdit.SelectionStart;
            lpreobject.clsid = guid;
            lpreobject.pstg = storage;
            lpreobject.poleobj = Marshal.GetIUnknownForObject(control);
            lpreobject.polesite = site;
            lpreobject.dvAspect = 1;
            lpreobject.dwFlags = 2;
            lpreobject.dwUser = 1;
            IRichEditOle.InsertObject(lpreobject);
            Marshal.ReleaseComObject(bytes);
            Marshal.ReleaseComObject(site);
            Marshal.ReleaseComObject(storage);

        }

        public bool InsertImageFromFile(string strFilename)
        {
            ILockBytes bytes;
            IStorage storage;
            IOleClientSite site;
            object obj2;
            NativeMethods.CreateILockBytesOnHGlobal(IntPtr.Zero, true, out bytes);
            NativeMethods.StgCreateDocfileOnILockBytes(bytes, 0x1012, 0, out storage);
            IRichEditOle.GetClientSite(out site);
            FORMATETC pFormatEtc = new FORMATETC();
            pFormatEtc.cfFormat = (CLIPFORMAT)0;
            pFormatEtc.ptd = IntPtr.Zero;
            pFormatEtc.dwAspect = DVASPECT.DVASPECT_CONTENT;
            pFormatEtc.lindex = -1;
            pFormatEtc.tymed = TYMED.TYMED_NULL;
            Guid riid = new Guid("{00000112-0000-0000-C000-000000000046}");
            Guid rclsid = new Guid("{00000000-0000-0000-0000-000000000000}");
            NativeMethods.OleCreateFromFile(ref rclsid, strFilename, ref riid, 1, ref pFormatEtc, site, storage, out obj2);
            if (obj2 == null)
            {
                Marshal.ReleaseComObject(bytes);
                Marshal.ReleaseComObject(site);
                Marshal.ReleaseComObject(storage);
                return false;
            }
            IOleObject pUnk = (IOleObject)obj2;
            Guid pClsid = new Guid();
            pUnk.GetUserClassID(ref pClsid);
            NativeMethods.OleSetContainedObject(pUnk, true);
            REOBJECT lpreobject = new REOBJECT();
            lpreobject.cp = _richEdit.TextLength;
            lpreobject.clsid = pClsid;
            lpreobject.pstg = storage;
            lpreobject.poleobj = Marshal.GetIUnknownForObject(pUnk);
            lpreobject.polesite = site;
            lpreobject.dvAspect = 1;
            lpreobject.dwFlags = 2;
            lpreobject.dwUser = 0;
            IRichEditOle.InsertObject(lpreobject);
            Marshal.ReleaseComObject(bytes);
            Marshal.ReleaseComObject(site);
            Marshal.ReleaseComObject(storage);
            Marshal.ReleaseComObject(pUnk);
            return true;
        }

        public REOBJECT InsertOleObject(
            IOleObject oleObject,
            int index)
        {
            if (oleObject == null)
            {
                return null;
            }

            ILockBytes pLockBytes;
            NativeMethods.CreateILockBytesOnHGlobal(IntPtr.Zero, true, out pLockBytes);

            IStorage pStorage;
            NativeMethods.StgCreateDocfileOnILockBytes(
                pLockBytes,
                (uint)(STGM.STGM_SHARE_EXCLUSIVE | STGM.STGM_CREATE | STGM.STGM_READWRITE),
                0,
                out pStorage);

            IOleClientSite pOleClientSite;
            IRichEditOle.GetClientSite(out pOleClientSite);

            Guid guid = new Guid();

            oleObject.GetUserClassID(ref guid);
            NativeMethods.OleSetContainedObject(oleObject, true);

            REOBJECT reoObject = new REOBJECT();

            reoObject.cp = _richEdit.TextLength;
            reoObject.clsid = guid;
            reoObject.pstg = pStorage;
            reoObject.poleobj = Marshal.GetIUnknownForObject(oleObject);
            reoObject.polesite = pOleClientSite;
            reoObject.dvAspect = (uint)DVASPECT.DVASPECT_CONTENT;
            reoObject.dwFlags = (uint)REOOBJECTFLAGS.REO_BELOWBASELINE;
            reoObject.dwUser = (uint)index;

            IRichEditOle.InsertObject(reoObject);

            Marshal.ReleaseComObject(pLockBytes);
            Marshal.ReleaseComObject(pOleClientSite);
            Marshal.ReleaseComObject(pStorage);

            return reoObject;
        }

        public void UpdateObjects()
        {
            int objectCount = this.IRichEditOle.GetObjectCount();
            for (int i = 0; i < objectCount; i++)
            {
                REOBJECT lpreobject = new REOBJECT();
                IRichEditOle.GetObject(i, lpreobject, GETOBJECTOPTIONS.REO_GETOBJ_ALL_INTERFACES);
                Point positionFromCharIndex = this._richEdit.GetPositionFromCharIndex(lpreobject.cp);
                Rectangle rc = new Rectangle(positionFromCharIndex.X, positionFromCharIndex.Y, 50, 50);
                _richEdit.Invalidate(rc, false);
            }
        }

        public void UpdateObjects(int pos)
        {
            REOBJECT lpreobject = new REOBJECT();
            IRichEditOle.GetObject(
                pos,
                lpreobject,
                GETOBJECTOPTIONS.REO_GETOBJ_ALL_INTERFACES);
            UpdateObjects(lpreobject);
        }

        public void UpdateObjects(REOBJECT reObj)
        {
            Point positionFromCharIndex = _richEdit.GetPositionFromCharIndex(
                    reObj.cp);
            Size size = GetSizeFromMillimeter(reObj);
            Rectangle rc = new Rectangle(positionFromCharIndex, size);
            _richEdit.Invalidate(rc, false);
        }

        private Size GetSizeFromMillimeter(REOBJECT lpreobject)
        {
            using (Graphics graphics = Graphics.FromHwnd(_richEdit.Handle))
            {
                Point[] pts = new Point[1];
                graphics.PageUnit = GraphicsUnit.Millimeter;

                pts[0] = new Point(
                    lpreobject.sizel.Width / 100,
                    lpreobject.sizel.Height / 100);
                graphics.TransformPoints(
                    CoordinateSpace.Device,
                    CoordinateSpace.Page,
                    pts);
                return new Size(pts[0]);
            }
        }
    }
    #endregion
}
